package online.inote.mapper.core;

import lombok.NoArgsConstructor;
import online.inote.mapper.annotation.GenerateMapper;
import online.inote.mapper.utils.ArraysUtils;
import online.inote.mapper.utils.ClassUtils;
import online.inote.mapper.utils.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.mapperhelper.MapperHelper;
import tk.mybatis.spring.mapper.MapperFactoryBean;
import tk.mybatis.spring.mapper.SpringBootBindUtil;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public class ExtendGenerator {
	
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	private ExtendMapperProperties props;
	private DefaultListableBeanFactory beanFactory;
	private MapperHelper mapperHelper = new MapperHelper();
	
	public static ExtendGenerator getInstance(BeanFactory beanFactory, MapperHelper mapperHelper, Environment environment) {
		ExtendGenerator generator = new ExtendGenerator();
		generator.beanFactory = (DefaultListableBeanFactory) beanFactory;
		generator.mapperHelper = mapperHelper;

		generator.loadProps(environment);
		
		return generator;
	}

	private void loadProps(Environment environment) {
		props = SpringBootBindUtil.bind(environment, ExtendMapperProperties.class, ExtendMapperProperties.PREFIX);
		
		if (props == null) {
			props = new ExtendMapperProperties();
		}
		
		Class<?> startClass = startClass();
		
		if (startClass != null) {
			String defaultPath = startClass.getPackage().getName();
			
			if (ArraysUtils.isEmpty(props.getScanModelPaths())) {
				logger.info("ExtendMapperProperties scanModelPaths is empty, default scan paths:[ {} ]", defaultPath);
				props.setScanModelPaths(new String[] {defaultPath});
			}
			
			if (ArraysUtils.isEmpty(props.getScanMapperPaths())) {
				logger.info("ExtendMapperProperties scanMapperPaths is empty, default scan paths:[ {} ]", defaultPath);
				props.setScanMapperPaths(new String[] {defaultPath});
			}
			
			if (ArraysUtils.isEmpty(props.getScanServicePaths())) {
				logger.info("ExtendMapperProperties scanServicePaths is empty, default scan paths:[ {} ]", defaultPath);
				props.setScanServicePaths(new String[] {defaultPath});
			}
		}
	}
	
	private Class<?> startClass() {
		Map<String, Object> beanMap = beanFactory.getBeansWithAnnotation(SpringBootApplication.class);
		
		if (!MapUtils.isEmpty(beanMap)) {
			return beanMap.get(beanMap.keySet().toArray(new String[] {})[0]).getClass();
		} 
		
		return null;
	}
	
	public void start() {
		Set<Class<?>> genericSet = scannerModel();
		
		if (!CollectionUtils.isEmpty(genericSet)) {
			generateMapper(genericSet);
			generateService(genericSet);
		}
	}
	
	private void generateMapper(Set<Class<?>> genericSet) {
		Set<Class<?>> existSet = scannerExistMapperSignature();
		
		logger.info("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
		logger.info("Extend-mapper: Generate mapper start...");
		
		genericSet.forEach(generic -> {
			if (!existSet.contains(generic)) {
				GenerateMapper generateMapper = generic.getAnnotation(GenerateMapper.class);
				
				try {
					Class<?> clazz = MapperGenerator.getInstance().build(generic, props.getSuperMapper());

					// 注册bean
					BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(clazz).getRawBeanDefinition();
					definition.setBeanClassName(MapperFactoryBean.class.getName());
					definition.getPropertyValues().add("mapperHelper", mapperHelper);
					definition.getPropertyValues().add("mapperInterface", clazz);

					definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(generateMapper.sqlSessionFactoryBeanName()));
					beanFactory.registerBeanDefinition(clazz.getSimpleName(), definition);
				} catch (Exception e) {
					throw new RuntimeException("Generate mapper fail!", e);
				}
			}
		});
		
		logger.info("Extend-mapper: Generate mapper end!");
		logger.info("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
	}
	
	private void generateService(Set<Class<?>> genericSet) {

		Set<Class<?>> existSet = scannerExistServiceSignature();
		
		logger.info("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
		logger.info("Extend-mapper: Generate service start...");
		
		genericSet.forEach(generic -> {
			if (!existSet.contains(generic)) {
				try {
					Class<?> clazz = ServiceGenerator.getInstance().build(generic, props.getSuperService());

					BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
					beanFactory.registerBeanDefinition(clazz.getSimpleName(), beanDefinitionBuilder.getBeanDefinition());
				} catch (Exception e) {
					throw new RuntimeException("Generate service fail!", e);
				}
			}
		});
		
		logger.info("Extend-mapper: Generate service end!");
		logger.info("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
	}

	private Set<Class<?>> scannerExistMapperSignature() {
		if (ArraysUtils.isEmpty(props.getScanMapperPaths())) {
			throw new RuntimeException("ExtendMapperProperties scanMapperPaths cannot be empty!");
		}
		
		return scanSignature(props.getSuperMapper(), props.getScanMapperPaths());
	}
	
	private Set<Class<?>> scannerExistServiceSignature() {
		if (ArraysUtils.isEmpty(props.getScanServicePaths())) {
			throw new RuntimeException("ExtendMapperProperties scanServicePaths cannot be empty!");
		}
		
		return scanSignature(props.getSuperService(), props.getScanMapperPaths());
	}

	private Set<Class<?>> scanSignature(Class<?> superClass, String[] packages) {
		Set<Class<?>> classSet = ClassUtils.scanClassBySuperClass(superClass, packages);
		classSet.remove(superClass);
		
		Set<Class<?>> existSet = new HashSet<>();
		
		if (!CollectionUtils.isEmpty(classSet)) {
			classSet.forEach(clazz -> existSet.add(ClassUtils.getGeneric(clazz)));
		}
		
		return existSet;
	}

	private Set<Class<?>> scannerModel() {
		if (ArraysUtils.isEmpty(props.getScanModelPaths())) {
			throw new RuntimeException("ExtendMapperProperties scanModelPaths cannot be empty!");
		}
		
		return ClassUtils.scanClassByAnnotation(GenerateMapper.class, props.getScanModelPaths());
	}
}